package com.aci.test.wordfreq.io;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.NoSuchElementException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class StreamWordsIterator implements Iterator<String>, Closeable {

	private static Logger logger = LogManager.getLogger(StreamWordsIterator.class);
	private static final String WORDS_SPLITTER = "\\s+";

	private BufferedReader bufferedReader;

	private String[] currentLine;
	private int currentWordIndex;

	public StreamWordsIterator(InputStream inputStream) {
		bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
	}

	@Override
	public boolean hasNext() {
		try {
			// check line buffer
			if (!isBufferExhausted()) {
				return true;
			}
			// and if not filled, trying to fill it to check the existence of
			// the next element
			return tryFillLineBuffer();
		} catch (IOException e) {
			logger.error("I/O error on bufferedReader", e);
			return false;
		}
	}

	@Override
	public String next() {
		try {
			if (!isBufferExhausted()) {
				return currentLine[currentWordIndex++];
			}
			// for users that not check hasNext()
			if (tryFillLineBuffer()) {
				return currentLine[currentWordIndex++];
			}
			throw new NoSuchElementException();
		} catch (IOException e) {
			logger.error("An I/O error occurred while reading line", e);
			throw new IllegalStateException("Next word is not available", e);
		}
	}

	private boolean isBufferExhausted() {
		return (null == currentLine) || (currentLine.length <= currentWordIndex);
	}

	/**
	 * Tries to fill line buffer
	 * 
	 * @return {@code true}, if line buffer is filled, or {@code false} if
	 *         stream end is reached and has no words more
	 * @throws IOException
	 *             If an I/O error occurs
	 */
	private boolean tryFillLineBuffer() throws IOException {
		while (bufferedReader.ready()) {
			String line = bufferedReader.readLine().trim();
			if (!line.isEmpty()) {				
				currentLine = line.split(WORDS_SPLITTER);
				currentWordIndex = 0;
				if (0 < currentLine.length) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public void remove() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void close() throws IOException {
		bufferedReader.close();
	}

}
